package com.elitesland.security.filter;

import cn.hutool.core.util.StrUtil;
import com.elitesland.core.exception.BadCaptchaException;
import com.elitesland.core.util.ParameterRequestWrapper;
import com.elitesland.core.util.RedisUtils;
import com.elitesland.security.LoginPostProcessor;
import com.elitesland.security.LoginTypeEnum;
import com.elitesland.security.handle.JsonAuthenticationEntryPoint;
import lombok.val;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import static org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY;
import static org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY;

/**
 * <pre>
 * [功能说明]
 * </pre>
 *
 * @author Mir
 * @date 2020/6/16
 */
public class PreLoginFilter extends GenericFilterBean {
    public static final String LOGIN_TYPE_KEY = "login_type";
    public static final String LOGIN_CAPTCHA_KEY = "captcha";
    public static final String LOGIN_CAPTCHA_UID = "capuid";

    private final RequestMatcher requiredAuthenticationRequestMatcher;
    private final Map<LoginTypeEnum, LoginPostProcessor> processors = new HashMap<>();
    private RedisUtils redisUtils = null;

    private final AuthenticationEntryPoint authenticationEntryPoint = new JsonAuthenticationEntryPoint();

    public PreLoginFilter(String loginProcessingUrl, Collection<LoginPostProcessor> loginPostProcessors, RedisUtils redisUtils) {
        Assert.notNull(loginProcessingUrl, "loginProcessingUrl must not be empty");
        requiredAuthenticationRequestMatcher = new AntPathRequestMatcher(loginProcessingUrl, "POST");
        this.redisUtils = redisUtils;
        val loginPostProcessor = defaultLoginProcessor();
        processors.put(loginPostProcessor.getLoginType(), loginPostProcessor);
        if (!CollectionUtils.isEmpty(loginPostProcessors)) {
            loginPostProcessors.forEach(e -> processors.put(e.getLoginType(), e));
        }
    }

    private LoginTypeEnum getTypeFromReq(ServletRequest request) {
        val parameter = request.getParameter(LOGIN_TYPE_KEY);

        var i = 1;
        if (parameter != null) {
            i = Integer.parseInt(parameter);
        }
        val values = LoginTypeEnum.values();
        return values[i];
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException, AuthenticationException {
        val parameterRequestWrapper = new ParameterRequestWrapper((HttpServletRequest) servletRequest);

        if (requiredAuthenticationRequestMatcher.matches((HttpServletRequest) servletRequest)) {
            val typeFromReq = getTypeFromReq(servletRequest);

            val loginPostProcessor = processors.get(typeFromReq);

            val username = loginPostProcessor.obtainsUsername(servletRequest);
            val password = loginPostProcessor.obtainPassword(servletRequest);
            val captchaStr = loginPostProcessor.obtainCaptcha(servletRequest);
            val capUid = loginPostProcessor.obtainCapUid(servletRequest);
            val code = (String) redisUtils.get(capUid);

            if (StrUtil.isBlank(code)) {
                authenticationEntryPoint.commence(
                        (HttpServletRequest) servletRequest,
                        (HttpServletResponse) servletResponse,
                        new BadCredentialsException("验证码不存在或已过期"));
                return;
            }

            redisUtils.del(capUid);
            if (StrUtil.isBlank(captchaStr) || !captchaStr.equalsIgnoreCase(code)) {
                authenticationEntryPoint.commence(
                        (HttpServletRequest) servletRequest,
                        (HttpServletResponse) servletResponse,
                        new BadCaptchaException("验证码错误")
                );
                return;
            }

            parameterRequestWrapper.setAttribute(SPRING_SECURITY_FORM_USERNAME_KEY, username);
            parameterRequestWrapper.setAttribute(SPRING_SECURITY_FORM_PASSWORD_KEY, password);
        }

        filterChain.doFilter(parameterRequestWrapper, servletResponse);
    }

    private LoginPostProcessor defaultLoginProcessor() {
        return new LoginPostProcessor() {
            @Override
            public LoginTypeEnum getLoginType() {
                return LoginTypeEnum.FORM;
            }

            @Override
            public String obtainsUsername(ServletRequest request) {
                return request.getParameter(SPRING_SECURITY_FORM_USERNAME_KEY);
            }

            @Override
            public String obtainPassword(ServletRequest request) {
                return request.getParameter(SPRING_SECURITY_FORM_PASSWORD_KEY);
            }

            @Override
            public String obtainCaptcha(ServletRequest request) {
                return request.getParameter(LOGIN_CAPTCHA_KEY);
            }

            @Override
            public String obtainCapUid(ServletRequest request) {
                return request.getParameter(LOGIN_CAPTCHA_UID);
            }
        };
    }
}
